#include <windows.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <iostream>

#include "wavread.h"

//-----------------------------------------------------------
// Play an audio stream on the default audio rendering
// device. The PlayAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data to the
// rendering device. The inner loop runs every 1/2 second.
//-----------------------------------------------------------

// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC  10000000
#define REFTIMES_PER_MILLISEC  10000

#define EXIT_ON_ERROR(hres)  \
    if (FAILED(hres)) { printf("error:\t%d\n", __LINE__); goto Exit; }

#define SAFE_RELEASE(punk)  \
    if ((punk) != NULL)  \
    { (punk)->Release(); (punk) = NULL; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);

class MyAudioSource {
    public:
        MyAudioSource() {
            wavReader->open_file("G:\\c++\\imguicom\\resource\\welcome.wav");
        };
        ~MyAudioSource() = default;

    public:
        HRESULT SetFormat(WAVEFORMATEX *pwfx) {
            WaveFormatExtensible fx = wavReader->get_fmtext();
            pwfx->wFormatTag = fx.FormatTag;
            pwfx->nChannels = fx.Channels;
            pwfx->cbSize = fx.ExSize;
            pwfx->nSamplesPerSec = fx.SampleRate;
            pwfx->nAvgBytesPerSec = fx.BytesRate;
            pwfx->wBitsPerSample = fx.BitsPerSample;
            pwfx->nBlockAlign = fx.BlockAlign;
            // std::cout << "SetFormat ====================>" << std::endl;
            // printf("wFormatTag:\t%d\n", pwfx->wFormatTag);
            // printf("nChannels:\t%d\n", pwfx->nChannels);
            // printf("cbSize:\t%d\n", pwfx->cbSize);
            // printf("nSamplesPerSec:\t%d\n", pwfx->nSamplesPerSec);
            // printf("nAvgBytesPerSec:\t%d\n", pwfx->nAvgBytesPerSec);
            // printf("wBitsPerSample:\t%d\n", pwfx->wBitsPerSample);
            // printf("nBlockAlign:\t%d\n", pwfx->nBlockAlign);
            return S_OK;
        };

        HRESULT LoadData(UINT32 bufferFrameCount, BYTE *pData, DWORD *flags) {
            int size = wavReader->read_data(pData, bufferFrameCount);
            // std::cout << "LoadData:\t" << size << std::endl;
            if (size == -1) {
                *flags = AUDCLNT_BUFFERFLAGS_SILENT;
            }
            return S_OK;
        };

    private:
        WAVEFORMATEX *m_pwfx;
        WaveReader *wavReader = new WaveReader;
};

HRESULT PlayAudioStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    REFERENCE_TIME hnsActualDuration;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX pwfx = { 0 };
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    UINT32 numFramesPadding;
    BYTE *pData;
    DWORD flags = 0;
    // 为当前线程初始化COM库并设置并发模式 
    // hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    hr = CoInitialize(NULL);
    EXIT_ON_ERROR(hr)

    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eRender, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(
                    IID_IAudioClient, CLSCTX_ALL,
                    NULL, (void**)&pAudioClient);
    EXIT_ON_ERROR(hr)

    // hr = pAudioClient->GetMixFormat(&pwfx);
    // EXIT_ON_ERROR(hr)

    // Tell the audio source which format to use.
    hr = pMySource->SetFormat(&pwfx);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->Initialize(
                         AUDCLNT_SHAREMODE_SHARED,
                         (AUDCLNT_STREAMFLAGS_RATEADJUST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY),
                         (hnsRequestedDuration * pwfx.nAvgBytesPerSec) / pwfx.nBlockAlign,
                         0,
                         &pwfx,
                         NULL);
    EXIT_ON_ERROR(hr)

    

    // Get the actual size of the allocated buffer.
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(
                         IID_IAudioRenderClient,
                         (void**)&pRenderClient);
    EXIT_ON_ERROR(hr)

    // Grab the entire buffer for the initial fill operation.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    // std::cout << "bufferFrameCount:\t" << bufferFrameCount << "\tnSamplesPerSec:\t" << pwfx.nSamplesPerSec << std::endl;

    // Load the initial data into the shared buffer.
    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
    EXIT_ON_ERROR(hr)

    // Calculate the actual duration of the allocated buffer.
    hnsActualDuration = (double)REFTIMES_PER_SEC *
                        bufferFrameCount / pwfx.nSamplesPerSec;

    hr = pAudioClient->Start();  // Start playing.
    EXIT_ON_ERROR(hr)

    // Each loop fills about half of the shared buffer.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Sleep for half the buffer duration.
        Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2 / 3000));

        // See how much buffer space is available.
        hr = pAudioClient->GetCurrentPadding(&numFramesPadding);
        EXIT_ON_ERROR(hr)

        numFramesAvailable = bufferFrameCount - numFramesPadding;

        // Grab all the available space in the shared buffer.
        hr = pRenderClient->GetBuffer(numFramesAvailable, &pData);
        EXIT_ON_ERROR(hr)

        // std::cout << "numFramesAvailable:\t" << numFramesAvailable << std::endl;

        // Get next 1/2-second of data from the audio source.
        hr = pMySource->LoadData(numFramesAvailable, pData, &flags);
        EXIT_ON_ERROR(hr)

        hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
        EXIT_ON_ERROR(hr)
    }

    // Wait for last data in buffer to play before stopping.
    Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2 / 3000 ));

    hr = pAudioClient->Stop();  // Stop playing.
    EXIT_ON_ERROR(hr)

Exit:
    //CoTaskMemFree(pwfx);
    // 反初始化
    CoUninitialize();
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pRenderClient)

    return hr;
}

int main() {
    MyAudioSource *pMySource = new MyAudioSource;
    PlayAudioStream(pMySource);
    // std::cout << "main end" << std::endl; 
    return 0;
}